package com.zhss.microservice.server.rebalance;

import com.zhss.microservice.server.node.Controller;

import java.util.*;

/**
 * 自动负载均衡的组件
 */
public class AutoRebalanceManager {

    private static final long AUTO_REBALANCE_THRESHOLD = 5 * 60 * 1000l;

    private AutoRebalanceManager() {

    }

    static class Singleton {
        private static AutoRebalanceManager instance = new AutoRebalanceManager();
    }

    public static AutoRebalanceManager getInstance() {
        return Singleton.instance;
    }

    /**
     * 发起负载均衡
     * @param joinedNodeId
     */
    public void rebalance(Integer joinedNodeId) {
        Controller controller = Controller.getInstance();
        long startTimestamp = controller.getStartTimestamp();
        long now = new Date().getTime();

        if(now - startTimestamp > AUTO_REBALANCE_THRESHOLD) {
            // 迁移数据的过程中之中，client客户端还需要来使用这些槽位呢，往里面注册数据
            // 心跳，等等
            // 最好的思路是什么呢，我们先重新去进行运算，分发到各个节点上去，如果那个节点上这些槽位已经有了
            // 最好是对那些槽位先进行一个初始化，把他作为一个临时副本的角色，就可以从主要槽位所在的节点去同步和复制数据过来
            // 等数据都同步和复制完毕了之后，这个时候就可以把你新复制的一些槽位进行转正

            // controller去看一下，哪个节点的槽位范围比较多，就是分配一些给新的节点
            // controller干脆把所有的槽位分配数据都重新计算一下，在做大规模的槽位的迁移和转移
            // hbase的region split分裂机制
            // split，controller从已有的节点里，随机挑选出来一个，然后把他负责的槽位范围，做一个切分
            // 切分过后，分配一部分的槽位给新的节点就可以了
            // 在切分的时候，不是均匀切分，先算出来要切分出多少个slot分配给新节点
            // 在选择slot的时候，可以就是说专门选一些空的slot分配给新节点，一个一个范围去分配的
            // 他会先把slot全部内存里初始化好，然后原始节点，此时会把有数据的slot的数据都发送到新节点去
            // 给新节点再去设置好，他的槽位范围的副本所在的副本节点的id

            Map<Integer, List<String>> slotsAllocation = controller.getSlotsAllocation();
            Map<Integer, Integer> replicaNodeIds = controller.getReplicaNodeIds();
            Integer randomIndex = new Random().nextInt(slotsAllocation.size());
            Integer currentIndex = 0;
            Integer selectedNodeId = null;
            List<String> selectedNodeSlots = null;

            for(Integer nodeId : slotsAllocation.keySet()) {
                if(currentIndex.equals(randomIndex)) {
                    selectedNodeId = nodeId;
                    selectedNodeSlots = slotsAllocation.get(nodeId);
                }
                currentIndex++;
            }

            // 推进一个槽位范围的切分
            if(selectedNodeSlots.size() > 1) {
                // 先处理槽位切分
                String selectedSlots = selectedNodeSlots.get(
                        new Random().nextInt(selectedNodeSlots.size()));
                selectedNodeSlots.remove(selectedSlots);

                List<String> joinedNodeSlots = new ArrayList<String>();
                joinedNodeSlots.add(selectedSlots);
                slotsAllocation.put(joinedNodeId, joinedNodeSlots);

                Integer replicaNodeId = replicaNodeIds.get(selectedNodeId);
                replicaNodeIds.put(joinedNodeId, replicaNodeId);

                // 同步最新集群元数据给candidate
                controller.syncSlotsAllocation();
                controller.syncReplicaNodeIds();

                // 通知新节点进行槽位初始化
                controller.sendNodeSlots(joinedNodeId);
                // 通知新节点更新自己的副本节点id
                controller.sendReplicaNodeId(joinedNodeId);
                // 通知目标节点，把指定范围的槽位，遍历一下，看里面有数据的话，就同步给新节点，同步完毕之后
                // 目标节点就可以把这个切分出去的槽位删除掉了
                controller.transferSlots(selectedNodeId, joinedNodeId, selectedSlots);
            } else if(selectedNodeSlots.size() == 1) {
                // 大家可以在这里的实现是一样的，就是说把你的一个槽位范围做一个切分
                // 把一半的槽位范围给你的目标节点就可以了
            }
        }
    }

}
